package com.gobrs.async.core.task;

import com.gobrs.async.core.TaskSupport;
import com.gobrs.async.core.callback.ErrorCallback;
import com.gobrs.async.core.common.def.DefaultConfig;
import com.gobrs.async.core.common.domain.AnyConditionResult;
import com.gobrs.async.core.common.domain.TaskResult;
import com.gobrs.async.core.common.enums.ExpState;
import com.gobrs.async.core.common.util.SystemClock;
import com.gobrs.async.core.config.ConfigManager;
import com.gobrs.async.core.log.LogTracer;
import com.gobrs.async.core.log.LogWrapper;
import com.gobrs.async.core.log.TraceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 每一个任务需要继承此 抽象类
 */
public abstract class AsyncTask<Param, Result> implements GobrsTask<Param, Result> {

    Logger logger = LoggerFactory.getLogger(AsyncTask.class);

    /**
     * 任务名称
     */
    private String name;

    /**
     * 任务描述
     */
    private String desc;

    private boolean callback = DefaultConfig.transaction;

    private int retryCount = DefaultConfig.retryCount;

    private boolean failSubExec = DefaultConfig.failSubExec;

    /**
     * if true => 当任何父任务完成时执行
     * 用来控制TaskActuator.upstreamDepdends的数量计算
     */
    private boolean any = false;

    /**
     * 根据线程调度执行的随机顺序进行执行，即谁先执行完 谁有资格继续往下执行，所以如果想根据执行结果的条件
     * 即：task 方法返回 true 则立即执行，返回false则不执行 的判断条件进行控制；可配置：A,B,C->D:anyCondition
     * 表示任意依赖的任务执行完成后立即执行自己
     */
    private boolean anyCondition = false;

    /**
     * 是否取消其运行中的直接父任务
     * /ɪkˈskluːsɪv/ adj.独有的,排外的
     */
    private boolean exclusive = false;

    /**
     * 获取任务结果
     * @param support the support
     * @return result result
     */
    public Result getResult(TaskSupport support) {
        return getResult(support, false);
    }

    public Result getResult(TaskSupport support, boolean anyCondition) {
        TaskResult<Result> taskResult = getTaskResult(support);
        if (anyCondition && taskResult != null) {
            return (Result) AnyConditionResult.builder().setResult(taskResult.getResult()).build();
        }
        if (taskResult != null) {
            return taskResult.getResult();
        }
        return null;
    }

    /**
     * 任务适配器结果
     *
     * @param param   the param
     * @param support the support
     * @return the result
     */
    public Result taskAdapter(Param param, TaskSupport support) {
        Long startTime = SystemClock.now();
        Result task;
        Exception exeError = null;
        try {
            task = task(param, support);
        } catch (Exception exception) {
            exeError = exception;
            throw exception;
        } finally {
            boolean costLogabled = ConfigManager.Action.costLogabled(support.getRuleName());
            if (costLogabled && Objects.nonNull(support.getLogWrapper())) {
                long costTime = SystemClock.now() - startTime;
                LogTracer logTracer = LogTracer.builder()
                        .taskName(this.getName())
                        .taskCost(costTime)
                        .executeState(exeError == null ? true : false)
                        .errorMessage(exeError == null ? "" : exeError.getMessage())
                        .build();
                LogWrapper logWrapper = support.getLogWrapper();
                logWrapper.addTrace(logTracer);
                logWrapper.setProcessCost(costTime);
            }
        }
        return task;
    }

    public abstract Result task(Param param, TaskSupport support);

    /**
     * 执行失败 回调
     * @param support   the support
     * @param exception the com.gobrs.async.exception
     */
    public void onFailureTrace(TaskSupport support, Exception exception) {
        boolean logable = ConfigManager.Action.errLogabled(support.getRuleName());
        if (logable) {
            logger.error("[traceId:{}] {} 任务执行失败", TraceUtil.get(), this.getName(), exception);
        }
        onFail(support, exception);
    }

    public <Result> Result getResult(TaskSupport support, Class<? extends ITask> clazz, Class<Result> type) {
        TaskResult<Result> taskResult = getTaskResult(support, clazz, type);
        if (taskResult != null) {
            return taskResult.getResult();
        }
        return null;
    }

    public TaskResult<Result> getTaskResult(TaskSupport support) {
        Map<Class, TaskResult> resultMap = support.getResultMap();
        return resultMap.get(this.getClass()) != null ? resultMap.get(this.getClass()) : resultMap.get(depKey(this.getClass()));
    }

    public <Result> TaskResult<Result> getTaskResult(TaskSupport support, Class<? extends ITask> clazz, Class<Result> type) {
        Map<Class, TaskResult> resultMap = support.getResultMap();
        return resultMap.get(clazz) != null ? resultMap.get(clazz) : resultMap.get(depKey(clazz));
    }

    public Param getParam(TaskSupport support) {
        Object taskResult = support.getParam();
        if (taskResult != null) {
            return (Param) taskResult;
        }
        return null;
    }

    public <Result> Future<Result> getTaskFuture(TaskSupport support, Class<? extends ITask> clazz, Class<Result> type) {
        Object o = support.getTaskLoader().futureMaps.get(clazz);
        if (Objects.nonNull(o)) {
            return ((Future<Result>) o);
        }
        return null;
    }

    public <Result> Object getTaskFutureResult(TaskSupport support, Class<? extends ITask> clazz, Class<Result> type, long timeout, TimeUnit unit) {
        Object o = support.getTaskLoader().futureMaps.get(clazz);
        if (o != null) {
            try {
                return ((Future<Result>) o).get(timeout, unit);
            } catch (Exception e) {
                logger.error("task {} getTaskFuture error {}", this.getName(), e);
            }
        }
        return null;
    }

    String depKey(Class clazz) {
        char[] cs = clazz.getSimpleName().toCharArray();
        cs[0] += 32;
        return String.valueOf(cs);
    }

    /**
     * 主动中断任务流程 API调用
     */
    public boolean stopAsync(TaskSupport support) {
        try {
            ErrorCallback errorCallback = new ErrorCallback(() -> support.getParam(), null, support, this);
            support.taskLoader.setExpCode(new AtomicInteger(ExpState.DEFAULT.getCode()));
            support.taskLoader.errorInterrupted(errorCallback);
        } catch (Exception ex) {
            logger.error("stopAsync error {}", ex);
            return false;
        }
        return true;
    }

    public boolean stopAsync(TaskSupport support, Integer expCode) {
        try {
            support.taskLoader.setIsRunning(false);
            support.taskLoader.setExpCode(new AtomicInteger(expCode));

            ErrorCallback errorCallback = new ErrorCallback(() -> support.getParam(), null, support, this);
            support.taskLoader.errorInterrupted(errorCallback);

        } catch (Exception ex) {
            logger.error("stopAsync error {} ", ex);
            return false;
        }
        return true;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isCallback() {
        return callback;
    }

    public void setCallback(boolean callback) {
        this.callback = callback;
    }

    public int getRetryCount() {
        return retryCount;
    }

    public void setRetryCount(int retryCount) {
        this.retryCount = retryCount;
    }

    public boolean isFailSubExec() {
        return failSubExec;
    }

    public void setFailSubExec(boolean failSubExec) {
        this.failSubExec = failSubExec;
    }

    public boolean isAny() {
        return any;
    }

    public void setAny(boolean any) {
        this.any = any;
    }

    public boolean isExclusive() {
        return exclusive;
    }

    public void setExclusive(boolean exclusive) {
        this.exclusive = exclusive;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public boolean isAnyCondition() {
        return anyCondition;
    }

    public void setAnyCondition(boolean anyCondition) {
        this.anyCondition = anyCondition;
    }

}
